Skip to content

feat(contexts): carry pages and their widgets through scheme export and import - #3201

Open
rubenvdlinde wants to merge 2 commits into
feat/noid/app-menu-export-importfrom
feat/noid/scheme-pages-and-widgets
Open

rubenvdlinde wants to merge 2 commits into
feat/noid/app-menu-export-importfrom
feat/noid/scheme-pages-and-widgets

Conversation

@rubenvdlinde

@rubenvdlinde rubenvdlinde commented Nov 2, 2026 •

Copy link
Copy Markdown

Part of #3099, on top of #3138 (menu export and import). An application's pages and their widgets now travel with its scheme, the same way the menu does: pages by technical name, and the table or view a widget shows by UUID.

Size: 778 lines: 465 code, 294 tests and 19 generated OpenAPI.

What changes

  • Export keeps the pages exactly as before and adds targetType (table or view) and targetUuid to each widget's source, next to the nodeRelId it already had. The old id still means nothing on another instance; the UUID does.
  • Import (importScheme, new optional pages parameter) applies each page of the scheme:
    • pages follow the same rule as the user migration import (importablePages()): only start pages and grid pages, and at most one start page, through one shared check
    • the start page of the scheme updates the start page of the application
    • a grid page updates the page with the same technical name, or is added. A new page keeps its technical name, unless it has the automatic form page_<id>, in which case it gets a name of its own
    • a page takes the title, icon and description of the scheme, and the widgets of a grid page replace its widgets
    • pages the scheme doesn't name stay as they are
  • Widgets are checked as the API would save them (GridWidgetTypes), through the same code the user migration import now uses. A widget may only show a table or view that is a node of the application once the scheme's nodes are imported, the same rule PageService follows. The import still adds the nodes the scheme lists, as before, but a widget never adds a node by itself. The user migration import (importContext) didn't change.
  • Skipped, each with a reason: a widget whose table or view isn't one of the application's resources after the import, a widget whose source has no UUID, a widget that wouldn't pass the API, a widget on the start page (only grid pages hold widgets), widgets past the 100 a page holds, a page that isn't a start page or grid page, and a second start page.
  • The menu is read after the pages, so a menu item can open a page the same import adds, including one with an automatic name, which gets a new name here. MenuService takes the page ids by the name the scheme gives them for this.
  • The preview (compareSchemeChanges) returns pages with from, to (the scheme pages with the kept widgets) and skipped. It counts as resources the nodes of the scheme the import would keep: ones the application has, ones the import adds, and ones that are here and manageable by the user. With the Resources switch off, the import checks the widgets again against the nodes that are sent. The menu preview no longer skips items for pages the import adds.
  • The import screen has a Pages switch next to the Menu switch. When it's on (the default), the pages from the preview are sent; when it's off, the application keeps its pages. Skipped pages and widgets are listed under "Skipped, not available here" as "Page: widget", like skipped menu items.

Porting note: the frontend follows the Menu section of #3138. The fork branch stack/s3-export-import was used as a reference for the import rules (match by technical name, widgets replace the page's widgets), but its model (emoji, title-based names, a separate scheme service, grid views) doesn't apply upstream.

This and #3202 both rework the node loop in importScheme(), so whichever is merged second needs a small manual merge. It's simplest to merge #3202 first: this one then only adds $nodeIds to the loop.

How it was tested

On Nextcloud 34 with PostgreSQL:

  • ContextPagesRoundTripTest: an application with a table, a view, a grid page with three widgets and a page with an automatic name, plus a menu item on that page, is exported, previewed against an empty application and imported into it once it holds the scheme's two nodes. The pages and widgets arrive on the table and view the scheme's nodes resolve to, the automatically named page gets a new name and the menu item follows it (without the page ids, this test fails), and two widgets are skipped with their reason in the preview and left out by the import: one on a table the user can't manage, and one on a table that is here and manageable but isn't a node of the scheme. The application ends up with only the scheme's two nodes. A second test previews a scheme whose table and view the import would add: widgets on them are kept, and a widget on a view that isn't here is skipped.
  • ContextServiceTest: the skip rules above, with their log lines
  • the full unit suite on PostgreSQL: 616 tests
  • composer cs:check, psalm, rector, npm run lint, npm run stylelint, npm run typescript:check and npm run build, all clean (one inherited eslint warning in src/store/store.js)
  • Cypress component test ImportContextSchemePreview.cy.js: the import sends the pages, leaves them out when the switch is off, and lists skipped pages and widgets.
  • on a live instance, through the API: exported an application with a table, a grid page with an automatic name and a data widget, and a menu item on that page. I added a widget on a table that is here but not in the application to the file, and imported it into an empty application. The preview listed that widget as skipped ("its table is not one of the resources of the application"). The import created the page under a new name with its widget on the node of the scheme, the menu item followed the page, and the other table didn't become a node.

🖼️ Screenshots

Screenshots follow in a comment.

🏁 Checklist

  • ⛑️ Tests (unit and/or integration) are included or not needed
  • 🔙 Backport requests are created or not needed: /backport to stableX.X
  • 📅 Milestone is set
  • 🌸 PR title is meaningful (if it should be in the changelog: is it meaningful to users?)

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

…nd import

A scheme now names the table or view of every widget source by
targetType and targetUuid. Importing it with pages updates the start
page and every grid page with the same technical name, adds the others,
and replaces the widgets of each grid page. A widget is checked as the
API would save it, and its table or view must be a node of the
application once the nodes of the scheme are imported; a widget never
adds a node. Skipped pages and widgets get a reason, in the log and in
the preview, and the menu is read after the pages so it can open a page
the same import adds. The import screen has a Pages switch next to the
Menu switch.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ruben van der Linde <ruben@conduction.nl>
Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ruben van der Linde <ruben@conduction.nl>
@rubenvdlinde
rubenvdlinde force-pushed the feat/noid/app-menu-export-import branch from 2471ff4 to 6368c26 Compare November 4, 2026 12:35
@rubenvdlinde
rubenvdlinde force-pushed the feat/noid/scheme-pages-and-widgets branch from 4010828 to 8da4db7 Compare November 4, 2026 12:35
@rubenvdlinde
rubenvdlinde marked this pull request as ready for review November 6, 2026 08:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant